這個主題將了解系統與檔案,像是介紹多種讀寫純文字檔和CSV格式檔案的方式,以及檔案的存取權限。
我們會實作一個命令列應用程式,可以接收各種 flag 及其引數,方便你控制程式、顯示說明文件等,並知道如何攔截作業系統所發出的中斷訊息,並在決定要在關閉程式前做什麼處理。
Go語言函式庫提供許多檔案操作的功能,如開啟檔案、建立、修改檔案等等,此外程式與系統間的互動也不局限於檔案本身,我們的程式也可以接收來自使用者的命令列旗標(flag),以便指定程式要做什麼。
就像我們在終端機使用過的一些命命工具 :
go build -o .\bin\hello_world.exe main.go
這行指令使用go build 來將 main.go 編譯成 \bin 子目錄下的 exe 執行檔,而執行檔的路徑和名稱就是透過flag -o(output)來指定
關於flag,Go語言提供 flag 套件來協助開發者。
func String(name string, value string, usage string) *string
func Bool(name string, value bool, usage string) *bool
func Int(name string, value int, usage string) *int
func Int64(name string, value int64, usage string) *int64
func Float64(name string, value float64, usage string) *float64
func Duration(name string, value time.Duration, usage string) *time.Duration
//分析所有命令行參數。
func Parse()
//Args 返回所有非 flag 的命令行參數切片,而 Arg 返回給定索引的非 flag 命令行參數。
func Args() []string
func Arg(i int) string
從以上就可以看出每個函示都有以下參數:
name : flag 名稱
value : flag 預設值
usage : 說明 flag 用途(也是字串),通常在你設定flag錯誤時,這個內容就會顯示給使用者看。
舉例 :
package main
import (
"fmt"
"flag"
)
func main() {
// 定義一個旗標 -value, 接收整數, 預設值為 -1
v := flag.Int("value", -1, "Need a value for the flag.")
flag.Parse()
fmt.Println(*v)
}
使用終端機指令 :
將以上檔案編成執行檔,再用flag:
如果執行程式時加上 -h ,或者不確定旗標型別,程式會列出可用旗標 :
如果輸入錯誤型別 :
更複雜的例子,這次程式最多能接收三個旗標,分別是name、age、married :
有時我們會希望某些flag是執行程式必要的參數,查無此flag就要提醒使用者,
這代表我們得要謹慎決定flag的預設值,因為我們會用這個預設值,來判斷使用者是否有加上該旗標或給予正確的值。
範例 :
package main
import (
"flag"
"fmt"
"os"
)
func main() {
// 定義一個旗標 -value, 接收整數, 預設值為 -1
n := flag.String("name", "", "your first name ?")
i := flag.Int("age", -1, "your age ?")
b := flag.Bool("married", false, "are you married ?")
flag.Parse()
if *n == "" { // 若名字旗標值為空字串,代表使用者沒有加上該旗標,或者未給值
fmt.Println("Name is required.")
flag.PrintDefaults() //印出所有旗標的預設值
os.Exit(1) //結束程式
}
fmt.Println("name : ", *n)
fmt.Println("age : ", *i)
fmt.Println("married : ", *b)
}
執行結果 :
如果沒有name flag :
可以注意到,-married 並沒有引數,但卻仍出現 true,這表示你可以用flag.Bool()定義一個不用引數的flag,讓flag本身當作一個"開關"。若沒加上 "-married" 效果等同 "-married=false"
在多數作業系統中,可以使用系統中斷訊號(signals)來對進程進行非標準的中斷,舉例來說:
當使用者在主控台按下 ctrl + c 時(^C),系統會送名為SIGINT的中斷(interrupt)訊號給程式,或者作業系統要強制終止程式,會傳送SIGTERM訊號給它,程式收到會立即結束,對於Go語言來說,就是執行 os.Exit(1)。
這樣的問題可能在於,程式中如果有使用defer延遲執行的函式,他們不會被執行。
而這些延遲執行的函式可能會負責以下:
總之可能導致必要檢查無法完成。
對此,我們可以在程式中註冊這些訊號,好在收到訊號時能井然有序完成該有的善後工作,確保程式正常結束。
Go 語言的 os/signal 套件提供了對這些訊號的處理功能。
如果希望程式能判斷它何時收到特定的作業系統訊號,得使用 signal 套件的 Notify() 函式來註冊 :
signal.Notify(<通知通道>, <訊號1>, <訊號2>....)
Notify 參數說明:
當註冊的訊號發生時,它會被傳入通道(channel),channel 是 Go 語言中專門用於非同步程式資料交換的管道。
接著是我們想接收到的訊號,這些都定義在 syscall 套件的常數中,如 syscall.SIGINT(中斷)、syscall.SIGTERM(終止)等。
為了能註冊和收到系統訊號,我們必須先建立一個通道和用 make() 初始化它,以便傳給 signal.Notify() 使用:
<通道> := make(chan os.Signal, 1)
chan(channel)關鍵字,代表我們要建立通道,其內容型別為 os.Signal (即系統訊號)。後面的 1 代表緩衝區(buffer)大小為1,也就是最多可以暫存 1 個訊號,若需要接收的類型比較多,可以在加大。
注意: buffer 至少必須為 1,否則程式嘗試讀取通道時很容易卡住。
建立好通道後,就可以用以下方是取一個值出來 :
<值> := <- <通道>
箭頭<-
:
代表受理算符(receive operators),意義是從通道中取出一個值,接著這裡用短變數宣告'將該值附值給一個變數。
練習 : 接收中斷訊號並結束程式
以下程式模擬攔截兩個常見訊號 : syscall.SIGINT、syscall.SIGTERM。
說明: 當使用這在主控台按下CTRL + C ,作業系統會傳送SIGINT中斷訊號給應用程式。於是我們註冊這個訊號,該訊號會被加入 channel 變數。當我們知道這個訊號存在,就可以自行控制程式如何結束。
以此例來說,我們會建立一個無窮迴圈,單純離開無窮迴圈,然後讓defer可以發揮作用。
以上練習中,展示Go語言可以攔截使用者觸發的中斷訊息或終止訊號,並讓程式正常結束,完成地必要善後工作。
關於Go的channel、非同步程序會在後續介紹,接下來將會先了解如何建立和寫入檔案~